package com.dctp.common.service;

import com.alibaba.fastjson.JSONObject;
import com.dctp.common.cache.*;
import com.dctp.common.cache.caffeine.AccountLimitCaffeine;
import com.dctp.common.cache.caffeine.FuturesCaffeine;
import com.dctp.common.core.RedisManager;
import com.dctp.common.disruptor.close.CloseOrder;
import com.dctp.common.disruptor.close.CloseOrderDisruptor;
import com.dctp.common.manager.ProjectManager;
import com.dctp.common.utils.*;
import com.dctp.common.vo.*;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.Db;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.dctp.common.vo.RedisKey.*;
import static com.dctp.common.vo.TipCode.*;

public class TradeService {

    private static final Logger logger = LoggerFactory.getLogger(TradeService.class);

    public static boolean saveFundFlow(Record user, FundChangeType changeType,
            BigDecimal balance, BigDecimal balanceAfter, BigDecimal changeAmount, Long orderId, String remark) {

        if (changeAmount == null || changeAmount.compareTo(BigDecimal.ZERO) == 0) {
            return true;
        }
        Long userId = user.getLong("id");
        Long parentId = user.getLong("parent_id");
        Long bottomRoleUserId = user.getLong("bottom_role_user_id");
        Long middleRoleUserId = user.getLong("middle_role_user_id");
        Long topRoleUserId = user.getLong("top_role_user_id");
        // 保证金流水
        LocalDateTime now = DateUtil.now();
        Record fundFlow = new Record()
                .set("user_id", userId)
                .set("balance_before", balance)
                .set("change_amount", changeAmount)
                .set("balance_after", balanceAfter)
                .set("change_type", changeType.getCode())
                .set("order_id", orderId)
                .set("remark", remark)
                .set("parent_id", parentId)
                .set("bottom_role_user_id", bottomRoleUserId)
                .set("middle_role_user_id", middleRoleUserId)
                .set("top_role_user_id", topRoleUserId)
                .set("create_time", now)
                .set("update_time", now);
        return Db.save("dctp_fund_flow", fundFlow);
    }


//    public static void executeTask(
//        Long userId,
//        Long orderId,
//        FundChangeType changeType,
//        BigDecimal balance,
//        BigDecimal balanceAfter,
//        BigDecimal changeAmount,
//        String remark) {
//
//        //先保存到redis，后续通过redis操作
//        FundFlowVo fundFlow = new FundFlowVo(userId,orderId,changeType.getCode(),balance,balanceAfter,changeAmount,remark);
//        RedisManager.getInstance().lpush(FUND_FLOW_KEY, JSON.toJSONString(fundFlow));
//    }


    /**
     * 计算盈亏（按tick_size和tick_value）
     * 
     * @param direction  方向 1=买多, 2=卖空
     * @param openPrice  开仓价
     * @param closePrice 平仓价
     * @param quantity   手数
     * @return 盈亏金额
     */
    public static BigDecimal calculatePnl(int direction, BigDecimal openPrice, BigDecimal closePrice, int quantity,
            Long futuresId) {

        // ✅ 业务代码使用
        Record futures = FuturesCaffeine.getFutures(futuresId);
        if (futures == null) {
            // 如果缓存没命中，可以 decide 要不要自动加载
            futures = FuturesCaffeine.refresh(futuresId);
        }

        if (futures == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal tickSize = futures.getBigDecimal("tick_size");
         
        BigDecimal tickValue = futures.getBigDecimal("tick_value");
        if (openPrice == null || closePrice == null
                || tickSize == null || tickValue == null
                || tickSize.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        // 获取汇率，没设置默认返回1
        BigDecimal rate = futures.getBigDecimal("rate");
        BigDecimal diff = (direction == 1)
                ? closePrice.subtract(openPrice)
                : openPrice.subtract(closePrice);
        BigDecimal ticks = diff.divide(tickSize, 8, RoundingMode.HALF_UP);

         return ticks.multiply(tickValue)
                .multiply(BigDecimal.valueOf(quantity)) // #10
                .multiply(rate) // #11 汇率转换
                .setScale(2, RoundingMode.HALF_UP);
    }

    /**
     * 封装下单的扣余额和写订单逻辑，成功返回订单Record，否则抛异常
     */
    public static Record handleOrder(Record user, Record contract, int direction, int orderSource,
            int quantity, BigDecimal openPrice, BigDecimal userInputPrice, BigDecimal marginOccupied,
            BigDecimal openFee) {
        Long userId = user.getLong("id");

        Long futuresId = contract.getLong("futures_id");
        String symbol = contract.getStr("symbol");
        BigDecimal lastPrice = ContractCache.getLastPriceBySymbol(symbol);
        if (lastPrice == null) {
            logger.error("无法获取最新价格，symbol: {}", symbol);
            return null;
        }
        if (null == openPrice) {
            openPrice = lastPrice;
        }
        // 判断是否挂单
        int status = 1; // 默认持仓中
        if (direction == 1) {
            if (userInputPrice != null) {
                if (userInputPrice.compareTo(lastPrice) > 0) {
                    openPrice = lastPrice; // 以最新价成交
                } else {
                    // 挂单
                    status = 0;
                    openPrice = userInputPrice;
                }
            } 
        } else if (direction == 2) {
            if (userInputPrice != null) {
                if (userInputPrice.compareTo(lastPrice) < 0) {
                    openPrice = lastPrice; // 以最新价成交
                } else {
                    // 挂单
                    status = 0;
                    openPrice = userInputPrice;
                }
            }
        }

        Integer orderType = status == 0 ? 2 : 1; // 1=开仓，2=挂单
        BigDecimal totalDeduct = marginOccupied;
        if (status == 1) {
            totalDeduct = totalDeduct.add(openFee); // 只有开仓时才扣除服务费
        }
        int rows = Db.update(
                "UPDATE dctp_user SET balance = balance - ? WHERE id = ? AND balance >= ?",
                totalDeduct, userId, totalDeduct);
        if (rows != 1) {
            logger.error("dctp_user balance fail . userId:{},balance:{}, totalDeduct:{}",
                    userId, user.getBigDecimal("balance"), totalDeduct);
            return new Record().set("error",BALANCE_NOT_ENOUGH);
        }

        // 写入订单
        // 注意：status=1 代表已开仓，0=挂单
        Long contractId = contract.getLong("id");
        LocalDateTime now = DateUtil.now();
        ProfitStat profitStat = ProfitStatCache.get(userId);
        BigDecimal initProfit = BigDecimal.ZERO;
        if (null != profitStat) {
            initProfit = profitStat.getInitEquity();
        }
        if (null == initProfit) {
            initProfit = BigDecimal.ZERO;
        }

        //计算所属分片
        int shardId = ShardUtil.calcShardId(userId);
        Long openTime = DateUtil.currentTimestamp();
        //分片ID
        // 初始权益
        return new Record()
                .set("user_id", userId)
                .set("contract_id", contractId)
                .set("futures_id", futuresId)
                .set("symbol", symbol)
                .set("direction", direction)
                .set("order_source", orderSource)
                .set("quantity", quantity)
                .set("open_price", openPrice)
                .set("open_time", openTime)
                .set("margin_occupied", marginOccupied)
                .set("open_service_fee", openFee)
                .set("status", status)
                .set("order_type", orderType)
                .set("create_time", now)
                .set("update_time", now)
                .set("shard_id", shardId)//分片ID
                .set("parent_id", user.getLong("parent_id"))
                .set("init_profit", initProfit)// 初始权益
                .set("bottom_role_user_id", user.getLong("bottom_role_user_id"))
                .set("middle_role_user_id", user.getLong("middle_role_user_id"))
                .set("top_role_user_id", user.getLong("top_role_user_id"));
    }

    /**
     * 下单限仓 / 风控校验
     *
     * @param userId   下单用户 ID
     * @param quantity 本次开仓手数
     * @throws RuntimeException 校验不通过时直接抛异常（外层 @Before(Tx.class) 会自动回滚并可捕获返回前端）
     */
    public static String checkAccountLimit(Long userId, int quantity) {

        Record dynParams = ProjectManager.getDynRiskParams(userId);
        //动态权益
        ProfitStat profitStat = ProfitStatCache.get(userId);
        BigDecimal dynForbidLine = NumberUtil.parseBigDecimal(dynParams.getStr("dyn_forbid_open_line"));
        if (null != profitStat) {
            BigDecimal dynamicEquity = profitStat.getDynamicEquity();
            if (dynForbidLine.compareTo(BigDecimal.ZERO) > 0) {
                if (dynamicEquity.compareTo(dynForbidLine) < 0) {
                    logger.warn("user:{},禁止开仓.dynamicEquity:{},dynForbidLine:{}", userId, dynamicEquity, dynForbidLine);
                    return ACCOUNT_LIMIT;
                }
            }
        }

        //当日亏损
        RedisManager Redis =  RedisManager.getInstance();
        LocalDateTime[] days = ProjectManager.getDayTime();
        if (null != profitStat) {
            Record lossParams = ProjectManager.getDayLossRiskParams(userId);
            BigDecimal lossForbidLine = NumberUtil.parseBigDecimal(lossParams.getStr("daily_loss_forbid_open"));
            BigDecimal floatingPnl = profitStat.getTotalPnl();

            String dailyPnlKey = String.format(KEY_DAY_PNL, userId);
            String redisPnl = RedisManager.getInstance().get(dailyPnlKey);

            if (StringUtil.isEmpty(redisPnl)) {
                // ✅ Redis 没有（缓存 MISS）→ 查库并回填
                BigDecimal dbPnl = Db.queryBigDecimal(
                        "SELECT ifnull(sum(realized_pnl), 0) FROM dctp_order WHERE user_id=? AND status=2 AND close_time >= ? AND close_time < ?",
                        userId, days[0], days[1]);
                redisPnl = dbPnl.toPlainString();
                Redis.setex(dailyPnlKey, DateUtil.getSecondsToEndOfDay(), redisPnl);
            }

            BigDecimal countPnl = NumberUtil.parseBigDecimal(redisPnl);
            BigDecimal totalPnl = countPnl.add(floatingPnl);  // floatingPnl 还是走 ProfitStat

            if (lossForbidLine.compareTo(BigDecimal.ZERO) > 0 && totalPnl.abs().compareTo(lossForbidLine) >= 0) {
                logger.warn("user:{},当日亏损超多禁开线,totalPnl:{},lossForbidLine:{}",userId,totalPnl,lossForbidLine);
                return ACCOUNT_LIMIT;
            }
        }


        // ✅ 2. Redis 获取实时数据
        String orderCountKey = String.format(KEY_ORDER_COUNT, userId);
        String dailyLotsKey = String.format(KEY_ORDER_LOTS, userId);
        String holdingLotsKey = String.format(KEY_HOLDING_LOTS, userId);

        Long todayOrderCount = Redis.getLong(orderCountKey);
        Long todayLots = Redis.getLong(dailyLotsKey);
        Long holdingLots = Redis.getLong(holdingLotsKey);

        // ✅ 3. 如果 Redis 缓存 miss，则查库回填
        if (todayOrderCount == null || todayLots == null || holdingLots == null) {
            Record agg = Db.findFirst(
                    "SELECT " +
                            "COUNT(*) AS order_count, " +
                            "SUM(quantity) AS total_lots, " +
                            "SUM(CASE WHEN status=1 THEN quantity ELSE 0 END) AS holding_lots " +
                            "FROM dctp_order " +
                            "WHERE user_id=? AND status < 3 AND create_time>=? AND create_time<?",
                    userId, days[0], days[1]);

            todayOrderCount = agg.getLong("order_count");
            if (null == todayOrderCount) {
                todayOrderCount = 0L;
            }
            todayLots = agg.getLong("total_lots");
            if (null == todayLots) {
                todayLots = 0L;
            }
            holdingLots = agg.getLong("holding_lots");
            if (null == holdingLots) {
                holdingLots = 0L;
            }

            // ✅ Redis 回填并设置过期时间到当天 0 点
            long ttl = DateUtil.getSecondsToEndOfDay();
            Redis.setex(orderCountKey, ttl, todayOrderCount);
            Redis.setex(dailyLotsKey, ttl, todayLots);
            Redis.setex(holdingLotsKey, ttl, holdingLots);
        }

        // ✅ 4. 获取限仓参数（Caffeine 缓存）
        Record limit = AccountLimitCaffeine.get(userId);
        if (null == limit) {
            return null;
        }

        // ✅ 5. 限仓校验逻辑
        /* ---------- ① 单笔手数上限 ---------- */
        int singleLotLimit = NumberUtil.parseInt(limit.get("single_order_lot_limit"), 0);
        if (singleLotLimit > 0 && quantity > singleLotLimit) {
            logger.warn("userId:{},单笔手数上限.singleLotLimit:{},quantity:{}",userId,singleLotLimit,quantity);
            return SINGLE_LOT_LIMIT;
        }

        /* ---------- ② 当日开仓笔数上限 ---------- */
        int dailyOrderLimit = NumberUtil.parseInt(limit.get("daily_order_limit"), 0);
        if (dailyOrderLimit > 0 && todayOrderCount + 1 > dailyOrderLimit) {
            logger.warn("userId:{},当日开仓笔数上限.todayCount:{},dailyOrderLimit:{}",userId,todayOrderCount,dailyOrderLimit);
            return DAILY_ORDER_LIMIT;
        }

        /* ---------- ③ 当日累计开仓手数 ---------- */
        int dailyLotLimit = NumberUtil.parseInt(limit.get("daily_max_open_lot"), 0);
        if (dailyLotLimit > 0 && todayLots + quantity > dailyLotLimit) {
            logger.warn("userId:{},当日累计开仓手数.todayLots:{}, quantity  :{},dailyLotLimit:{}",userId,todayLots,quantity,dailyLotLimit);
            return DAILY_LOT_LIMIT;
        }

        /* ---------- ④ 当前持仓手数上限 ---------- */
        int maxPositionLot = NumberUtil.parseInt(limit.get("max_position_lot"), 0);
        if (maxPositionLot > 0 && holdingLots + quantity > maxPositionLot) {
            logger.warn("userId:{},当前持仓手数上限.holdingLots:{}, quantity:{},maxPositionLot:{}",userId,holdingLots,quantity,maxPositionLot);
            return MAX_POSITION_LOT;
        }
        return null;
    }

    public static CloseOrder handleCloseOrder(Record order, Record user, BigDecimal closePrice, int closeQuantity,Integer orderType) {

        Long userId = user.getLong("id");
        Long orderId = order.getLong("id");
        BigDecimal marginOccupied = order.getBigDecimal("margin_occupied");
        // 计算实际盈亏
        BigDecimal realizedPnl =
                calculatePnl(order.getInt("direction"),order.getBigDecimal("open_price"),
                        closePrice,closeQuantity,order.getLong("futures_id"));

        // 释放保证金+结算盈亏+扣除平仓手续费
        BigDecimal closeServiceFee = order.getBigDecimal("open_service_fee");
        if (closeServiceFee == null) {
            closeServiceFee = BigDecimal.ZERO;
        }
        //10000 -20000 - 1000 = -11000,   balance:5000
        BigDecimal refund = marginOccupied.add(realizedPnl).subtract(closeServiceFee);

        BigDecimal userBalance = user.getBigDecimal("balance");
        BigDecimal afterBalance = userBalance.add(marginOccupied).add(realizedPnl);
        BigDecimal afterBalance2 = userBalance.subtract(closeServiceFee);
        if (refund.compareTo(BigDecimal.ZERO) < 0 && refund.abs().compareTo(userBalance) > 0 ) {

            //处理余额为负的问题
            String shouldRefund = refund.toPlainString();
            String primaryPnl = realizedPnl.toPlainString();

            BigDecimal gap = refund.abs().subtract(userBalance);
            refund = userBalance.negate();
            realizedPnl = realizedPnl.add(gap);

            afterBalance = BigDecimal.ZERO;
            afterBalance2 = BigDecimal.ZERO;
            logger.warn("穿仓处理，穿仓用户：{},穿仓额度：{},现有余额:{},原应返还:{},实际返还:{},实际亏损：{},处理后亏损:{}",
                    user.getStr("email"),
                    gap,
                    userBalance,
                    shouldRefund,
                    refund,
                    primaryPnl,
                    realizedPnl);
            }

        Record updateUserRecord = new Record();
        updateUserRecord.set("refund",refund);
        updateUserRecord.set("balance",user.getBigDecimal("balance"));
        updateUserRecord.set("id",userId);

        LocalDateTime now = DateUtil.now();
        Record orderUpdate = new Record()
                .set("id", orderId)
                .set("close_price", closePrice)
                .set("symbol",order.getStr("symbol"))
                .set("order_type", orderType)
                .set("close_quantity", closeQuantity)
                .set("close_service_fee", closeServiceFee)
                .set("realized_pnl", realizedPnl)
                .set("status", 2)
                .set("user_id",userId)
                .set("shard_id",order.getStr("shard_id"))
                .set("update_time", now)
                .set("close_time", now);

        Record feeFundFlow = new Record()
                .set("user_id", userId)
                .set("order_id", orderId)
                .set("change_type", FundChangeType.FEE.getCode())
                .set("balance_before", userBalance)
                .set("balance_after", afterBalance2)
                .set("change_amount", closeServiceFee.negate())
                .set("remark", "平仓服务费")
                .set("parent_id", order.getLong("parent_id"))
                .set("top_role_user_id", order.getLong("top_role_user_id"))
                .set("middle_role_user_id", order.getLong("middle_role_user_id"))
                .set("bottom_role_user_id", order.getLong("bottom_role_user_id"))
                .set("create_time", now)
                .set("update_time", now);

        CloseOrder closeOrder = new CloseOrder(orderUpdate,updateUserRecord,null,feeFundFlow);


        if (realizedPnl.compareTo(BigDecimal.ZERO) != 0) {
            Record closeFundFlow = new Record()
                    .set("user_id", userId)
                    .set("order_id", orderId)
                    .set("change_type", FundChangeType.CLOSE.getCode())
                    .set("balance_before", userBalance)
                    .set("balance_after", afterBalance)
                    .set("change_amount", realizedPnl)
                    .set("remark", "平仓结算")
                    .set("parent_id", order.getLong("parent_id"))
                    .set("top_role_user_id", order.getLong("top_role_user_id"))
                    .set("middle_role_user_id", order.getLong("middle_role_user_id"))
                    .set("bottom_role_user_id", order.getLong("bottom_role_user_id"))
                    .set("create_time", now)
                    .set("update_time",now);
            closeOrder.setOrderFundFlow(closeFundFlow);
        }

        //发布到disruptor处理
        CloseOrderDisruptor.publish(closeOrder);
        return closeOrder;
    }


    public static void publishCloseOrder(Record order) {

        try {
            Map<String,Object> pubMessage = new HashMap<>();
            pubMessage.put("data", order.toMap());
            pubMessage.put("type", OrderEventType.DELETE);
            pubMessage.put("shard", order.getLong("shard_id"));

            RedisManager.getInstance().publish(
                    RedisKey.PUB_SUB_TRADE_ORDER,
                    JSONObject.toJSONString(pubMessage)
            );
        } catch (Exception e) {
            logger.error("❌ 订单发布Redis消息失败: {}", e.getMessage(), e);
        }
    }


    public static void publishOpenOrder(Record order) {

//        try {
//            Map<String,Object> pubMessage = new HashMap<>();
//            pubMessage.put("data", order.toMap());
//            pubMessage.put("type", OrderEventType.ADD);
//            pubMessage.put("shard", order.getLong("shard_id"));
//            RedisManager.getInstance().publish(
//                    RedisKey.PUB_SUB_TRADE_ORDER,
//                    JSONObject.toJSONString(pubMessage)
//            );
//        } catch (Exception e) {
//            logger.error("❌ 订单发布Redis消息失败: {}", e.getMessage(), e);
//        }
    }



    /**
     * ✅ 下单成功后，更新 Redis 计数器
     */
    public static void afterOrderCreated(List<Record> orders) {

        RedisManager Redis = RedisManager.getInstance();
        long expireAt = DateUtil.endOfTodayEpoch();

        Redis.pipeline(pipeline -> {
            for (Record order : orders) {

                Long userId = order.getLong("user_id");
                String orderCountKey = String.format(KEY_ORDER_COUNT, userId);
                String dailyLotsKey = String.format(KEY_ORDER_LOTS, userId);
                String holdingLotsKey = String.format(KEY_HOLDING_LOTS, userId);

                Integer quantity = order.getInt("quantity");
                // ✅ 1. INCR 下单笔数
                Redis.incr(orderCountKey);
                Redis.expireAt(orderCountKey, expireAt);  // 设置过期时间到 0 点

                // ✅ 2. INCR 今日累计手数
                Redis.incrBy(dailyLotsKey, quantity);
                Redis.expireAt(dailyLotsKey, expireAt);

                // ✅ 3. INCR 持仓手数
                Redis.incrBy(holdingLotsKey, quantity);
                Redis.expireAt(holdingLotsKey, expireAt);
            }
        });
    }

    /**
     * ✅ 平仓成功后，更新 Redis 计数器（减少持仓手数）
     */
    public static void afterOrderClosed(List<CloseOrder> orders) {

        RedisManager Redis = RedisManager.getInstance();
        long expireAt = DateUtil.endOfTodayEpoch();
        Redis.pipeline(pipeline -> {
            for (CloseOrder closeOrder : orders) {
                Record order = closeOrder.getOrder();
                Long userId = order.getLong("user_id");
                Integer quantity = order.getInt("quantity");
                BigDecimal realizedPnl = order.getBigDecimal("realized_pnl");
                // ✅ 更新持仓手数
                String holdingLotsKey = String.format(KEY_HOLDING_LOTS, userId);
                Long newVal = Redis.decrBy(holdingLotsKey, quantity);
                if (newVal < 0) {
                    Redis.set(holdingLotsKey, "0");
                }
                Redis.expireAt(holdingLotsKey,expireAt);

                // ✅ 更新当日亏损
                String dailyPnlKey = String.format(KEY_DAY_PNL, userId);
                Redis.incrByFloat(dailyPnlKey, realizedPnl.doubleValue());
                Redis.expireAt(dailyPnlKey, expireAt);
            }
        });
    }


    /**
     * ✅ 平仓成功后，更新 Redis 计数器（减少持仓手数）
     */
    public static void afterOrderClosedWithFocusOrder(List<Record> orders) {

        RedisManager Redis = RedisManager.getInstance();
        long expireAt = DateUtil.endOfTodayEpoch();
        Redis.pipeline(pipeline -> {
            for (Record order : orders) {
                Long userId = order.getLong("user_id");
                Integer quantity = order.getInt("quantity");
                BigDecimal realizedPnl = order.getBigDecimal("realized_pnl");
                // ✅ 更新持仓手数
                String holdingLotsKey = String.format(KEY_HOLDING_LOTS, userId);
                Long newVal = Redis.decrBy(holdingLotsKey, quantity);
                if (newVal < 0) {
                    Redis.set(holdingLotsKey, "0");
                }
                Redis.expireAt(holdingLotsKey,expireAt);

                // ✅ 更新当日亏损
                String dailyPnlKey = String.format(KEY_DAY_PNL, userId);
                Redis.incrByFloat(dailyPnlKey, realizedPnl.doubleValue());
                Redis.expireAt(dailyPnlKey, expireAt);
            }
        });
    }
}
